package com.seleniti.enpareja.android.control.controller;

import java.lang.Thread.UncaughtExceptionHandler;
import java.net.ConnectException;

import org.ioc.commons.flowcontrol.exceptionparser.ExceptionParserManagerImpl;
import org.ioc.commons.flowcontrol.logincontroller.LoginController;
import org.ioc.commons.flowcontrol.messageboxfactory.MessageBoxFactory;
import org.ioc.commons.flowcontrol.messageboxfactory.MessageBoxFactory.ConfirmResponse;
import org.ioc.commons.japtsy.shared.model.biz.JAPTSYServiceException;
import org.ioc.commons.japtsy.shared.model.biz.JAPTSYServiceException.ErrorCode;
import org.ioc.commons.utils.Format;
import org.ioc.commons.utils.FormatterLogger;

import android.os.Looper;

import com.seleniti.enpareja.android.control.factory.ClientFactoryImpl;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.common.exception.EnParejaException;
import com.seleniti.enpareja.controller.control.controller.LoginFailedCause;
import com.seleniti.enpareja.controller.control.controller.LoginParams;
import com.seleniti.enpareja.controller.control.factory.ClientFactory;
import com.seleniti.enpareja.controller.control.factory.EnParejaExceptionController;

public class EnParejaExceptionControllerImpl implements EnParejaExceptionController {
	private static FormatterLogger logger = FormatterLogger.getLogger(EnParejaExceptionControllerImpl.class);

	private final UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {

		@Override
		public void uncaughtException(Thread thread, Throwable e) {
			EnParejaExceptionControllerImpl.this.handleException(e, null);

			if (androidDefaultHandler != null) {
				androidDefaultHandler.uncaughtException(thread, e);
			}
		}
	};

	private final ExceptionParserManagerImpl<EnParejaException> exceptionParser = new ExceptionParserManagerImpl<EnParejaException>();

	private final MessageBoxFactory messageBoxFactory;

	private UncaughtExceptionHandler androidDefaultHandler;

	private boolean outtodateConfirm;

	private ClientFactory clientFactory;

	private LoginController<LoggedUserInfo, LoginFailedCause, LoginParams, String> loginController;

	public EnParejaExceptionControllerImpl(ClientFactoryImpl clientFactory) {
		this.clientFactory = clientFactory;
		this.messageBoxFactory = clientFactory.getMessageBoxFactory().forThisObject(
				clientFactory.getAppContextProvider().getContext());
		this.loginController = clientFactory.getLoginController();
	}

	@SuppressWarnings("incomplete-switch")
	@Override
	public void handleException(Throwable e, String msgInfo, Object... params) {
		/**
		 * Logamos la excepción. Este log sólo aparece a la hora de depurar. Una
		 * vez compilada la aplicación no es logada.
		 */

		String msg = Format.substitute("Error inesperado");
		if (msgInfo != null && !msgInfo.isEmpty()) {
			msg = Format.substitute("Error inesperado: {0}", Format.substitute(msgInfo, params));
		} else if (e.getMessage() != null && !e.getMessage().isEmpty()) {
			msg = e.getMessage();
		}

		logger.error(e, msg);

		do {
			if (e instanceof JAPTSYServiceException) {
				ErrorCode code = ((JAPTSYServiceException) e).getErrorCode();

				switch (code) {
				case INVALID_DOMAIN_CLASS:
					if (!this.outtodateConfirm) {
						this.outtodateConfirm = true;
						final Runnable r = new Runnable() {

							@Override
							public void run() {
								messageBoxFactory
										.confirm(
												"Versión desactualizada",
												"Parece que tienes una versión desactualizada de la aplicación. Para que siga funcionando debes actualizarla.\n¿Deseas hacerlo ahora?",
												new ConfirmResponse() {

													@Override
													public void onUserResponse(boolean userResponse) {
														outtodateConfirm = false;
														if (userResponse) {
															clientFactory.getShell().goToMarketPlace();
														}

													}
												});
							}
						};
						runThread(r);

					}
					sleep();
					return;
				case SERVICE_ERROR:
					EnParejaException epe = (e instanceof EnParejaException ? (EnParejaException) e : fromMessage(
							EnParejaException.class, ((JAPTSYServiceException) e).getDetailedInfo()));

					if (epe != null) {
						EnParejaException.ErrorCode epCode = epe.getErrorCode();

						if (epCode != null) {
							switch (epCode) {
							case LOGGIN_REQUIRED:
								loginController.refreshLoggedUserInfo();
								final Runnable r = new Runnable() {

									@Override
									public void run() {
										messageBoxFactory.info("Sesión caducada",
												"Demasiado tiempo sin usar la aplicación. Inténtalo de nuevo.");
									}
								};
								runThread(r);
								sleep();
								return;
							}
						}

					}
					break;
				case UNABLE_TO_GET_ENTITY_BY_ID:
					final Runnable r = new Runnable() {

						@Override
						public void run() {
							messageBoxFactory
									.info("Elemento eliminado",
											"Parece que ese elemento ya no existe. ¿Lo eliminó tu pareja?");
							clientFactory.getShell().refreshNow();
						}
					};
					runThread(r);
					sleep();
					return;

				}

			} else if (e instanceof ConnectException) {
				final Runnable r = new Runnable() {

					@Override
					public void run() {
						messageBoxFactory.info("enpareja.net",
								"No se pudo conectar al servidor. Inténtalo de nuevo cuando tengas conexión.");
						clientFactory.getShell().retryButtonVisibility().setVisible(true);
					}
				};
				runThread(r);
				sleep();
				return;
			}

		} while ((e = e.getCause()) != null);

		final String finalMsg = msg;
		final Runnable r = new Runnable() {

			@Override
			public void run() {
				messageBoxFactory.error("enpareja.net", finalMsg);
			}
		};
		runThread(r);

		sleep();
	}

	private void runThread(final Runnable r) {
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				try {
					r.run();
				} finally {
					Looper.loop();
				}
			}
		}.start();
	}

	protected void sleep() {
		try {
			Thread.sleep(5000); // Let the message box display before app will
								// get
								// shutdown
		} catch (InterruptedException ex) {
			// Ignored.
		}
	}

	public UncaughtExceptionHandler getHandler() {
		return handler;
	}

	public <EX extends EnParejaException> void registerExceptionParser(Class<EX> clazz,
			org.ioc.commons.flowcontrol.exceptionparser.ExceptionParserManager.ExceptionParser<EX> parser) {
		exceptionParser.registerExceptionParser(clazz, parser);
	}

	public <EX extends EnParejaException> EX fromMessage(Class<EX> clazz, String message) {
		return exceptionParser.fromMessage(clazz, message);
	}

	@Override
	public <EX extends EnParejaException> void unregisterExceptionParser(Class<EX> exceptionClass) {
		exceptionParser.unregisterExceptionParser(exceptionClass);
	}

	public void setAsDefaultUncaughtExceptionHandler() {
		this.androidDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this.getHandler());
	}
}
